﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HotShot.Service.Entities;

namespace HotShot.Service.BusinessLogic
{
    public class ClusterProcessorLogic
    {
        public static ClusterProcessorLogic Current = new ClusterProcessorLogic();

        public Random RND = new Random();
        public int MaxHash { get; set; }

        public string ProcessorId { get; set; }
        public int? HashKey { get; set; }

        Dictionary<string, ProcessorNodeDescriptor> knownNodes = new Dictionary<string, ProcessorNodeDescriptor>();

        public List<ProcessorNodeDescriptor> GetKnownNodes(bool listOnlyActives, bool addCurrent)
        {
            var result = knownNodes.Where(i => !listOnlyActives || i.Value.IsActive).Select(i => i.Value).ToList();
            if (addCurrent) result.Add(GetSelfDescriptor());


            return result.OrderBy(i => i.HashKey).ToList();
        }

        public void SetKnownNodes(List<ProcessorNodeDescriptor> nodes, bool setActive)
        {
            nodes.ForEach(n =>
            {
                if (n.NodeId != ProcessorId)
                {
                    if (!knownNodes.ContainsKey(n.NodeId))
                    {
                        Trace.WriteLine("Added known node: " + n.NodeId);
                        n.IsActive = true;
                        n.Ticks = DateTime.UtcNow.Ticks;
                        knownNodes.Add(n.NodeId, n);
                    }
                    else if (knownNodes[n.NodeId].HashKey != n.HashKey)
                    {
                        Trace.WriteLine("Updated hask key for " + n.NodeId + " to " + n.HashKey);
                        knownNodes[n.NodeId].HashKey = n.HashKey;
                    }

                    if (!knownNodes[n.NodeId].IsActive && setActive)
                    {
                        Trace.WriteLine("Node set to active " + n.NodeId + " to " + n.HashKey);
                        knownNodes[n.NodeId].IsActive = true;
                    }
                }
            });
        }

        public void Initialize()
        {
            MaxHash = 1000;
        }

        public ProcessorNodeDescriptor GetSelfDescriptor()
        {
            return new ProcessorNodeDescriptor
            {
                NodeId = this.ProcessorId,
                HashKey = this.HashKey,
            };
        }

        public void UpdateTicks(string nodeId, long ticks)
        {
            if (nodeId != null && knownNodes.ContainsKey(nodeId))
            {
                knownNodes[nodeId].Ticks = ticks;
                knownNodes[nodeId].IsActive = true;
            }
        }

        public int Rnd(int min, int max) {
            var result = RND.Next(min, max);
            return result;
        }

        public int RndW(int min, int max)
        {
            var result = -1;
            while (result < min || result >= max)
            {
                result = min + RND.Next((max - min) / 2 + 1) + RND.Next((max - min) / 2 + 1);
            }

            return result;
        }

        public void CalculateHashKey()
        {
            var nodes = GetKnownNodes(true, false).Where(i => i.HashKey != null).ToArray();

            if (nodes.Length == 0)
            {
                HashKey = Rnd(0, MaxHash);
                Trace.WriteLine(">>> Single node, random hash key: " + ClusterProcessorLogic.Current.HashKey);
            }
            else if (nodes.Length == 1)
            {
                HashKey = (RndW(0, MaxHash) + nodes[0].HashKey) % MaxHash;
                Trace.WriteLine(">>> Second node, random hash key around the clock: " + ClusterProcessorLogic.Current.HashKey);
            }
            else
            {
                int min, max;
                GetMaxRange(nodes, out min, out max);
                HashKey = RndW(min, max) % MaxHash;

                Trace.WriteLine(String.Format(
                    ">>> Multiple nodes, random hash key between {0} and {1}: {2}", min, max, ClusterProcessorLogic.Current.HashKey));
            }
        }

        private void GetMaxRange(ProcessorNodeDescriptor[] nodes, out int min, out int max)
        {
            Debug.Assert(nodes.Length >= 2);
            min = nodes[0].HashKey ?? 0;
            max = nodes[1].HashKey ?? 0;
            for (int i = 0; i < nodes.Length; i++)
            {
                int i1 = (i + 1) % nodes.Length;
                int i2 = (i + 2) % nodes.Length;

                if ((nodes[i2].HashKey + MaxHash - nodes[i1].HashKey) % MaxHash > (max + MaxHash - min) % MaxHash)
                {
                    min = nodes[i1].HashKey ?? 0;
                    max = nodes[i2].HashKey ?? 0;
                }
            }

            if (max < min) max = max + MaxHash;
        }

        public List<ProcessorNodeDescriptor> GetNodesForEntityKey(string key, int nodeCount)
        {
            var nodes = GetKnownNodes(true, true);
            var hash = key.GetHashCode() % MaxHash;
            int first = nodes.IndexOf(nodes.FirstOrDefault(i => i.HashKey >= hash));
            List<ProcessorNodeDescriptor> result = new List<ProcessorNodeDescriptor>();

            if (nodeCount > nodes.Count) 
                nodeCount = nodes.Count;

            for (int j = first; j < first + nodeCount; j++)
            {
                result.Add(nodes[j % nodes.Count]);
            }

            return result;
        }
    }
}
